/******************************************************************************

	COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2010.

	This file is part of the Maxsi Library.

	Maxsi Library is free software: you can redistribute it and/or modify it
	under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or (at your
	option) any later version.

	Maxsi Library is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
	License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with Maxsi Library. If not, see <http://www.gnu.org/licenses/>.

	Maxsi Library
	A powerful Cross-Platform C++ General Purpose Library that allows you to
	efficiently create high-performance and reliable applications.

	MaxsiBufferStreamer.cpp
	A simple system for accessing a buffer or stream in a sync manner, where
	the source might not even be locally available, and may origin from another
	process, a file, a network socket, a virtual filesystem, or just a buffer
	in the process' address space.

******************************************************************************/

#include "MaxsiLibrary.h"

BeginMaxsiNamespace

//=============================================================================
//	MaxsiBufferStreamer
//=============================================================================

MAXSI_DEFINE_IMPLEMENTATION(MaxsiBufferStreamer, IBufferStreamer, MAXSI_INTERFACE_DYNAMIC);

MaxsiBufferStreamer::MaxsiBufferStreamer()
{
	MyBuffer		=	NULL;
	MyBufferSize	=	0;
	MyBufferUsed	=	0;
	MyPosition		=	0;
}

MaxsiBufferStreamer::~MaxsiBufferStreamer()
{
	if ( MyBuffer ) { delete[] MyBuffer; }
}

int MaxsiBufferStreamer::GetFlags()
{
	return ( MAXSI_BUFFER_STREAMER_FLAG_READABLE | MAXSI_BUFFER_STREAMER_FLAG_WRITABLE | MAXSI_BUFFER_STREAMER_FLAG_RESIZABLE | MAXSI_BUFFER_STREAMER_FLAG_KNOWN_LENGTH | MAXSI_BUFFER_STREAMER_FLAG_SEEKABLE ); 
}

FileSize_t MaxsiBufferStreamer::GetSize()
{
	return MyBufferSize;
}

MaxsiError MaxsiBufferStreamer::SetSize(FileSize_t NewSize)
{
	// Attempt to allocate a bufferful of our new size!
	BYTE* NewBuffer		=	new BYTE[NewSize];

	if ( NewBuffer == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	// Copy as much of the old content as the new buffer can hold!
	if ( MyBuffer != NULL )
	{
		if ( NewSize < MyBufferSize )
		{
			memcpy(NewBuffer, MyBuffer, NewSize);
			MyBufferUsed = NewSize; // We lost some of our precious data!
		}
		else
		{
			memcpy(NewBuffer, MyBuffer, MyBufferSize);
		}
	}

	delete[] MyBuffer;
	MyBuffer = NewBuffer;
	MyBufferSize = NewSize;

	return MAXSI_ERROR_SUCCESS;	
}

FileSize_t MaxsiBufferStreamer::GetPosition()
{
	return MyPosition;
}

MaxsiError MaxsiBufferStreamer::Seek(FileSize_t Offset)
{
	if ( Offset > SIZE_MAX ) { return MAXSI_ERROR_FILE_TOO_BIG; }
	
	MaxsiError Result = MAXSI_ERROR_SUCCESS;

	// If we seeked beyond the end of the buffer, just expand the buffer to that point, and a little more.
	if ( Result == MAXSI_ERROR_SUCCESS && Offset > MyBufferSize ) { Result = SetSize(Offset + 8192); }

	if ( Result == MAXSI_ERROR_SUCCESS ) { MyPosition = Offset; }

	return Result;
}

MaxsiError MaxsiBufferStreamer::Read(BYTE* Destination, size_t DestinationSize, size_t* ActualRead)
{
	if ( Destination == NULL ) { return MAXSI_ERROR_INVALID_INPUT; }

	// See if we have encountered an EOF condition.
	if ( MyPosition >= MyBufferUsed ) { return MAXSI_ERROR_END_OF_STREAM; }

	// Find out how much data is available!
	size_t Available = (MyBufferUsed - MyPosition < DestinationSize) ? MyBufferUsed : DestinationSize; 

	memcpy(Destination, MyBuffer + MyPosition, Available);

	MyPosition += Available;

	// If the caller isn't living dangerously, return how much data was actually read.
	if ( ActualRead != NULL ) { *ActualRead = Available; }

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiBufferStreamer::ReadFrom(FileSize_t Offset, BYTE* Destination, size_t DestinationSize, size_t* ActualRead)
{
	if ( Offset >= MyBufferUsed ) { return MAXSI_ERROR_INVALID_INPUT; }

	Seek(Offset); // Seek cannot fail in this case.

	return Read(Destination, DestinationSize, ActualRead);
}

MaxsiError MaxsiBufferStreamer::Write(BYTE* Source, size_t SourceSize)
{
	MaxsiError Result	=	MAXSI_ERROR_SUCCESS;

	// Check if we are going to write outside our bounds!
	if ( MyBufferUsed + SourceSize >= MyPosition )
	{
		// It seems so, simply increase our buffer a little.
		Result = SetSize(MyBufferSize + SourceSize + 8192);

		if ( Result != MAXSI_ERROR_SUCCESS ) { return Result; }
	}

	// Now let's put this buffer where it belongs!
	memcpy(MyBuffer + MyBufferUsed, Source, SourceSize);

	// Mark the written buffer space as used.
	MyBufferUsed += SourceSize;

	return Result;
}

MaxsiError MaxsiBufferStreamer::WriteTo(FileSize_t Offset, BYTE* Source, size_t SourceSize)
{
	MaxsiError Result	=	Seek(Offset);

	if ( Result != MAXSI_ERROR_SUCCESS ) { return Result; }

	return Write(Source, SourceSize);
}

MaxsiError MaxsiBufferStreamer::WriteFlush()
{
	// We aren't using this, since this class doesn't do any buffering.
	return MAXSI_ERROR_SUCCESS;
}

void MaxsiBufferStreamer::SetBuffer(BYTE* NewBuffer, size_t NewBufferSize, size_t NewBufferUsed)
{
	if ( MyBuffer ) { delete[] MyBuffer; }

	MyBuffer		=	NewBuffer;
	MyBufferSize	=	NewBufferSize;
	MyBufferUsed	=	NewBufferUsed;
}

//=============================================================================
//	MaxsiFileStreamer
//=============================================================================

MAXSI_DEFINE_IMPLEMENTATION(MaxsiFileStreamer, IBufferStreamer, MAXSI_INTERFACE_DYNAMIC);

MaxsiFileStreamer::MaxsiFileStreamer()
{
	MyFileSystem	=	NULL;
	MyFileHandle	=	NULL;
	MyPosition		=	0;
}

MaxsiFileStreamer::MaxsiFileStreamer(IFileSystem* NewFileSystem, MaxsiHandle NewFileHandle)
{
	MyFileSystem	=	NewFileSystem;
	MyFileHandle	=	NewFileHandle;
	MyPosition		=	0;
}

MaxsiFileStreamer::~MaxsiFileStreamer()
{
	if ( MyFileHandle ) { MyFileSystem->CloseFile(MyFileHandle); }
}

int MaxsiFileStreamer::GetFlags()
{
	return ( MAXSI_BUFFER_STREAMER_FLAG_READABLE | MAXSI_BUFFER_STREAMER_FLAG_WRITABLE | MAXSI_BUFFER_STREAMER_FLAG_RESIZABLE | MAXSI_BUFFER_STREAMER_FLAG_KNOWN_LENGTH | MAXSI_BUFFER_STREAMER_FLAG_SEEKABLE ); 
}

FileSize_t MaxsiFileStreamer::GetSize()
{
	return MyFileSystem->GetFileSize(MyFileHandle);
}

MaxsiError MaxsiFileStreamer::SetSize(FileSize_t NewSize)
{
	return MyFileSystem->SetFileSize(MyFileHandle, NewSize);
}

FileSize_t MaxsiFileStreamer::GetPosition()
{
	return MyPosition;
}

MaxsiError MaxsiFileStreamer::Seek(FileSize_t Offset)
{
	MAXSI_TODO("We might not be returning the correct error code here!");
	if ( MyFileSystem->SeekFile(MyFileHandle, Offset) ) { MyPosition = Offset; return MAXSI_ERROR_SUCCESS; } else { return MAXSI_ERROR_UNSPECIFIED; }
}

MaxsiError MaxsiFileStreamer::Read(BYTE* Destination, size_t DestinationSize, size_t* ActualRead)
{
	size_t Read = MyFileSystem->ReadFile(MyFileHandle, Destination, DestinationSize);

	if ( ActualRead != NULL ) { *ActualRead = Read; }

	MAXSI_TODO("We might not be returning the correct error code here!");

	if ( Read == 0 ) { return MAXSI_ERROR_UNSPECIFIED; }

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiFileStreamer::ReadFrom(FileSize_t Offset, BYTE* Destination, size_t DestinationSize, size_t* ActualRead)
{
	MaxsiError Result = Seek(Offset);

	if ( Result != MAXSI_ERROR_SUCCESS ) { return Result; }

	return Read(Destination, DestinationSize, ActualRead);
}

MaxsiError MaxsiFileStreamer::Write(BYTE* Source, size_t SourceSize)
{
	size_t Written = MyFileSystem->WriteFile(MyFileHandle, Source, SourceSize);

	MAXSI_TODO("We might not be returning the correct error code here!");

	if ( Written == 0 ) { return MAXSI_ERROR_UNSPECIFIED; }

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiFileStreamer::WriteTo(FileSize_t Offset, BYTE* Source, size_t SourceSize)
{
	MaxsiError Result	=	Seek(Offset);

	if ( Result != MAXSI_ERROR_SUCCESS ) { return Result; }

	return Write(Source, SourceSize);
}

MaxsiError MaxsiFileStreamer::WriteFlush()
{
	MAXSI_TODO_LOW("We aren't using this, since this class doesn't do any buffering. (The OS, however, does. Should we make the OS flush to disk, then?");
	return MAXSI_ERROR_SUCCESS;
}

EndMaxsiNamespace

